package cz.boxdesign.core.dao;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;

import cz.boxdesign.core.dao.query.Query;
import cz.boxdesign.core.domain.Entity;
import cz.boxdesign.core.jdbc.DatabaseOperation;

/**
 *
 * @author jlattenberk
 *
 * @param <T>
 */
public abstract class EntityDaoAbstract<T extends Entity, Q extends Query> implements EntityDao<T> {

  protected final Log log = LogFactory.getLog(EntityDaoAbstract.class);

  @Autowired
  public DatabaseOperation jdbc;

  @Override
  public List<T> getAll() throws DatabaseOperationException {
    return getAll("");
  }

  @Override
  public T getById(Integer id) throws DatabaseOperationException {
    List<T> result = getAll("and id = " + id);
    if (result.size() == 0) {
      throw new DatabaseOperationException("Incorret entityId. No Entity with id=" + id);
    }
    return result.get(0);
  }

  @Override
  public List<T> getByClause(String clause) throws DatabaseOperationException {
    return getAll(clause);
  }

  @Override
  public void updateEntity(T entity) throws DatabaseOperationException {
    insertOrUpdate(entity, Q.UPDATE);
  }

  @Override
  public void insertEntity(T entity) throws DatabaseOperationException {
    insertOrUpdate(entity, Q.INSERT);
  }

  @Override
  public void deleteEntity(T entity) throws DatabaseOperationException {
    insertOrUpdate(entity, Q.DELETE);
  }

  @Override
  public void batchUpdate(List<T> entityList) throws DatabaseOperationException {
    if (entityList != null) {
      batch(entityList, Q.UPDATE);
    }
  }

  @Override
  public void batchInsert(List<T> entityList) throws DatabaseOperationException {
    if (entityList != null) {
      if (!entityList.isEmpty()) {
        batch(entityList, Q.INSERT);
      }
    }
  }

  @Override
  public void batchDelete(List<T> entityList) throws DatabaseOperationException {
    if (entityList != null) {
      batch(entityList, Q.DELETE);
    }
  }

  /**
   *
   * @param clause
   * @return
   * @throws DatabaseOperationException
   */
  List<T> getAll(String clause) throws DatabaseOperationException {
    List<T> result = null;
    try {
      jdbc.beginTransaction();
      String sql = getSqlQuery(Q.SELECT) + clause;
      log.info(sql);
      ResultSet rs = jdbc.getSatement().executeQuery(sql);
      result = new ArrayList<T>();
      getEntityList(result, rs);
      rs.close();
      jdbc.endTransaction();
    } catch (SQLException e) {
      jdbc.rollback();
      jdbc.reconect();
      log.info("RECONNECT");
      throw new DatabaseOperationException(e.getMessage());
    }
    return result;
  }

  /**
   * @param entity
   * @param update
   * @throws DatabaseOperationException
   */
  void insertOrUpdate(T entity, int queryValue) throws DatabaseOperationException {
    try {
      jdbc.beginTransaction();
      String sql = getSqlQuery(queryValue);
      log.info(sql);
      PreparedStatement ps = jdbc.getPreparedStatement(sql);
      fillStatement(entity, queryValue, ps);
      ps.executeUpdate();
      ps.close();
      jdbc.endTransaction();
    } catch (SQLException e) {
      e.printStackTrace();
      jdbc.rollback();
      jdbc.reconect();
      log.info("RECONNECT");
      throw new DatabaseOperationException("Insert/Update of entity " + entity.toString() + "failed!");
    }
  }

  /**
   * @param entityList
   * @param update
   * @throws DatabaseOperationException
   */
  void batch(List<T> entityList, int queryValue) throws DatabaseOperationException {
    try {
      String sql = getSqlQuery(queryValue);
      log.info(sql);
      PreparedStatement ps = jdbc.getPreparedStatement(sql);
      for (T entity : entityList) {
        fillStatement(entity, queryValue, ps);
        ps.addBatch();
      }
      ps.executeBatch();
      ps.close();
      jdbc.endTransaction();
    } catch (SQLException e) {
      jdbc.rollback();
      jdbc.reconect();
      log.info("RECONNECT");
      throw new DatabaseOperationException("Batch operation failed!");
    }
  }

  /**
   * @param result
   * @param rs
   * @throws SQLException
   */
  abstract void getEntityList(List<T> result, ResultSet rs) throws SQLException, DatabaseOperationException;

  /**
   *
   * @param queryEnum
   * @return
   */
  abstract String getSqlQuery(int queryValue);

  /**
   * @param entity
   * @param clause
   * @param ps
   * @throws SQLException
   */
  abstract void fillStatement(T entity, int queryValue, PreparedStatement ps) throws SQLException;
}
